/* 
 * File:   main.cc
 * Author: johannes
 *
 * Created on December 17, 2013, 8:46 AM
 */

#include "include/Window.h"
#include "include/VideoManager.h"
#include "include/Map.h"
#include "include/Surface.h"
#include "include/Texture.h"
#include <cstdlib>
#include <SDL2/SDL.h>
#include <iostream>
#include <stdexcept>
#include <assert.h>
#include "TmxParser/Tmx.h"
#include "angelscript.h"
#include "anglescript/scriptstdstring/scriptstdstring.h"
#include "anglescript/scriptbuilder/scriptbuilder.h"
#include "anglescript/autowrapper/aswrappedcall.h"

using namespace GUL;
using GUL::System::Graphic::Surface;
using GUL::System::Graphic::Texture;


///
void MessageCallback(const asSMessageInfo *msg, void *param);
void ConfigureEngine(asIScriptEngine *engine);
int CompileScript(asIScriptEngine *engine);
void LineCallback(asIScriptContext *ctx, unsigned int *timeOut);

void HelloWorld(void);
// Function implementation with native calling convention
void PrintString(std::string &str);
// Function implementation with generic script interface
void PrintString_Generic(asIScriptGeneric *gen);

// Function wrapper is needed when native calling conventions are not supported
void timeGetTime_Generic(asIScriptGeneric *gen);
unsigned int timeGetTime();
int getch() ;
///
void example(void);


int main(int argc, char **argv)
{
    System::Graphic::SDLLib lib;
    lib.InitVideoSupport();
    int r;

	// Create the script engine
	asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
        
	if( engine == 0 )
	{
		std::cout << "Failed to create script engine." << std::endl;
		return -1;
	}

	// The script compiler will write any compiler messages to the callback.
	engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);

	// Configure the script engine with all the functions, 
	// and variables that the script should be able to use.
	ConfigureEngine(engine);
	
	// Compile the script code
	r = CompileScript(engine);
	if( r < 0 )
	{
		engine->Release();
		return -1;
	}

	// Create a context that will execute the script.
	asIScriptContext *ctx = engine->CreateContext();
	if( ctx == 0 ) 
	{
		std::cout << "Failed to create the context." << std::endl;
		engine->Release();
		return -1;
	}

	// We don't want to allow the script to hang the application, e.g. with an
	// infinite loop, so we'll use the line callback function to set a timeout
	// that will abort the script after a certain time. Before executing the 
	// script the timeOut variable will be set to the time when the script must 
	// stop executing. 
	unsigned int timeOut;
//	r = ctx->SetLineCallback(asFUNCTION(LineCallback), &timeOut, asCALL_CDECL);
//	if( r < 0 )
//	{
//		std::cout << "Failed to set the line callback function." << std::endl;
//		ctx->Release();
//		engine->Release();
//		return -1;
//	}

	// Find the function for the function we want to execute.
	asIScriptFunction *func = engine->GetModule(0)->GetFunctionByDecl("int main()");
	if( func == 0 )
	{
		std::cout << "The function 'int main()' was not found." << std::endl;
		ctx->Release();
		engine->Release();
		return -1;
	}

	// Prepare the script context with the function we wish to execute. Prepare()
	// must be called on the context before each new script function that will be
	// executed. Note, that if you intend to execute the same function several 
	// times, it might be a good idea to store the function returned by 
	// GetFunctionByDecl(), so that this relatively slow call can be skipped.
	r = ctx->Prepare(func);
	if( r < 0 ) 
	{
		std::cout << "Failed to prepare the context." << std::endl;
		ctx->Release();
		engine->Release();
		return -1;
	}

	// Set the timeout before executing the function. Give the function 1 sec
	// to return before we'll abort it.
	timeOut = timeGetTime() + 1000;

	// Execute the function
	std::cout << "Executing the script." << std::endl;
	std::cout << "---" << std::endl;
	r = ctx->Execute();
	std::cout << "---" << std::endl;
	if( r != asEXECUTION_FINISHED )
	{
		// The execution didn't finish as we had planned. Determine why.
		if( r == asEXECUTION_ABORTED )
			std::cout << "The script was aborted before it could finish. Probably it timed out." << std::endl;
		else if( r == asEXECUTION_EXCEPTION )
		{
			std::cout << "The script ended with an exception." << std::endl;

			// Write some information about the script exception
			asIScriptFunction *func = ctx->GetExceptionFunction();
			std::cout << "func: " << func->GetDeclaration() << std::endl;
			std::cout << "modl: " << func->GetModuleName() << std::endl;
			std::cout << "sect: " << func->GetScriptSectionName() << std::endl;
			std::cout << "line: " << ctx->GetExceptionLineNumber() << std::endl;
			std::cout << "desc: " << ctx->GetExceptionString() << std::endl;
		}
		else
			std::cout << "The script ended for some unforeseen reason (" << r << ")." << std::endl;
	}
	else
	{
		// Retrieve the return value from the context
		int returnValue = ctx->GetReturnByte();
		std::cout << "The script function returned: " << returnValue << std::endl;
	}

	// We must release the contexts when no longer using them
	ctx->Release();

	// Release the engine
	engine->Release();

	return 0;
    
    
    
    return 0;
}

void MessageCallback(const asSMessageInfo *msg, void *param)
{
	const char *type = "ERR ";
	if( msg->type == asMSGTYPE_WARNING ) 
		type = "WARN";
	else if( msg->type == asMSGTYPE_INFORMATION ) 
		type = "INFO";

	printf("%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
}

void ConfigureEngine(asIScriptEngine *engine)
{
	int r;
        
        
	// Register the script string type
	// Look at the implementation for this function for more information  
	// on how to register a custom string type, and other object types.
	RegisterStdString(engine);
        
        System::Graphic::Window::AddScriptEngine(engine);
        System::Graphic::VideoManager::AddScriptEngine(engine);
        
        


	if( !strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY") )
	{
		// Register the functions that the scripts will be allowed to use.
		// Note how the return code is validated with an assert(). This helps
		// us discover where a problem occurs, and doesn't pollute the code
		// with a lot of if's. If an error occurs in release mode it will
		// be caught when a script is being built, so it is not necessary
		// to do the verification here as well.
                r = engine->RegisterGlobalFunction("void HelloWorld()", asFUNCTION(HelloWorld),asCALL_CDECL); assert ( r >= 0 );
		r = engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString), asCALL_CDECL); assert( r >= 0 );
		r = engine->RegisterGlobalFunction("uint GetSystemTime()", asFUNCTION(timeGetTime), asCALL_STDCALL); assert( r >= 0 );
                r = engine->RegisterGlobalFunction("void example()", asFUNCTION(example), asCALL_STDCALL); assert( r >= 0 );
                
                
                
        
        }
	else
	{
		// Notice how the registration is almost identical to the above. 
            r = engine->RegisterGlobalFunction("void HelloWorld()", asFUNCTION(HelloWorld),asCALL_GENERIC); assert ( r >= 0 );
		r = engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString_Generic), asCALL_GENERIC); assert( r >= 0 );
		r = engine->RegisterGlobalFunction("uint GetSystemTime()", asFUNCTION(timeGetTime_Generic), asCALL_GENERIC); assert( r >= 0 );
	}


	// It is possible to register the functions, properties, and types in 
	// configuration groups as well. When compiling the scripts it then
	// be defined which configuration groups should be available for that
	// script. If necessary a configuration group can also be removed from
	// the engine, so that the engine configuration could be changed 
	// without having to recompile all the scripts.
}

int CompileScript(asIScriptEngine *engine)
{
	int r;

	// We will load the script from a file on the disk.
	FILE *f = fopen("script.as", "rb");
	if( f == 0 )
	{
		std::cout << "Failed to open the script file 'script.as'." << std::endl;
		return -1;
	}

	// Determine the size of the file	
	fseek(f, 0, SEEK_END);
	int len = ftell(f);
	fseek(f, 0, SEEK_SET);

	// On Win32 it is possible to do the following instead
	// int len = _filelength(_fileno(f));

	// Read the entire file
	std::string script;
	script.resize(len);
	int c =	fread(&script[0], len, 1, f);
	fclose(f);

	if( c == 0 ) 
	{
		std::cout << "Failed to load script file." << std::endl;
		return -1;
	}

	// Add the script sections that will be compiled into executable code.
	// If we want to combine more than one file into the same script, then 
	// we can call AddScriptSection() several times for the same module and
	// the script engine will treat them all as if they were one. The script
	// section name, will allow us to localize any errors in the script code.
	asIScriptModule *mod = engine->GetModule(0, asGM_ALWAYS_CREATE);
	r = mod->AddScriptSection("script", &script[0], len);
	if( r < 0 ) 
	{
		std::cout << "AddScriptSection() failed" << std::endl;
		return -1;
	}
	
        
	// Compile the script. If there are any compiler messages they will
	// be written to the message stream that we set right after creating the 
	// script engine. If there are no errors, and no warnings, nothing will
	// be written to the stream.
	r = mod->Build();
	if( r < 0 )
	{
		std::cout << "Build() failed" << std::endl;
		return -1;
	}

	// The engine doesn't keep a copy of the script sections after Build() has
	// returned. So if the script needs to be recompiled, then all the script
	// sections must be added again.

	// If we want to have several scripts executing at different times but 
	// that have no direct relation with each other, then we can compile them
	// into separate script modules. Each module use their own namespace and 
	// scope, so function names, and global variables will not conflict with
	// each other.

	return 0;
}

void LineCallback(asIScriptContext *ctx, unsigned int *timeOut)
{
	// If the time out is reached we abort the script
	if( *timeOut < timeGetTime() )
		ctx->Abort();

	// It would also be possible to only suspend the script,
	// instead of aborting it. That would allow the application
	// to resume the execution where it left of at a later 
	// time, by simply calling Execute() again.
}

void HelloWorld(void)
{
    std::cout<<"Hello World\n";
}
// Function implementation with native calling convention
void PrintString( std::string &str)
{
	std::cout << str;
}

// Function implementation with generic script interface
void PrintString_Generic(asIScriptGeneric *gen)
{
	std::string *str = (std::string*)gen->GetArgAddress(0);
	std::cout << *str;
}

// Function wrapper is needed when native calling conventions are not supported
void timeGetTime_Generic(asIScriptGeneric *gen)
{
	gen->SetReturnDWord(timeGetTime());
}

unsigned int timeGetTime()
        {
                timeval time;
                gettimeofday(&time, NULL);
                return time.tv_sec*1000 + time.tv_usec/1000;
        }

int getch() 
        {
                struct termios oldt, newt;
                int ch;

                tcgetattr(STDIN_FILENO, &oldt);
                newt = oldt;
                newt.c_lflag &= ~( ICANON | ECHO );
                tcsetattr( STDIN_FILENO, TCSANOW, &newt );

                ch = getchar();

                tcsetattr( STDIN_FILENO, TCSANOW, &oldt );
                return ch;
        }

void example(void)
{
    try
    {
        //Init------------------------------------------------------------------
        static System::Graphic::VideoManager Manager("Hello World",
                System::UPair(SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED),
                System::UPair(1000, 800),
                SDL_WINDOW_SHOWN);
        System::Graphic::Map*         Map;
        System::UPair                 size(0,0);
        SDL_Event e;
        //Setting---------------------------------------------------------------
        Manager["Hello World"].InitSDLRenderSupport();
        Map = new System::Graphic::Map(Manager["Hello World"],"Labyrinth.tmx");
        //Pre-Loop--------------------------------------------------------------
        Map->DrawOnTexture();
        Manager["Hello World"]("Map").QueryTexture(nullptr,size);  
        System::Rect place(Manager["Hello World"].Size().first/2-size.first/2,
                           Manager["Hello World"].Size().second/2-size.second/2,
                                 size.first,size.second);
        // Game Loop -----------------------------------------------------------
        while (Manager["Hello World"].IsValid())
        {
                    Manager["Hello World"].Clean();         
                    Manager["Hello World"]( Manager["Hello World"]("Map"),
                                            (nullptr),
                                            (place));
                    Manager["Hello World"].Update();
                    Manager["Hello World"].HandleInput();   
        }        
        

    }

    catch (std::out_of_range& e)
    {
        std::cerr << e.what();
    }
    catch (std::invalid_argument& e)
    {
        std::cerr << "An invalid argument was used somewhere an exception is caught: " << e.what();
    }
    catch (std::runtime_error& e)
    {
        std::cerr << "Runtime exceptions is caught: " << e.what();
    }
    catch (std::logic_error& e)
    {
        std::cerr << "Logic error in the programm exceptions is caught...\nYour Programmer did something really stupid. " << e.what();
    }
}






//            for (System::WindowIter iter = Manager().begin(); iter != Manager().end(); iter++)
//            {
//                (iter->second)->Clean();
//                //                    iter->second->WriteInfoData("Hello World Test Writing\n");
//                iter->second->operator ()(iter->second->operator ()("Image"), nullptr, nullptr);
//                iter->second->operator ()(iter->second->operator ()("Image"), nullptr, nullptr);
//                iter->second->Update();
//                iter->second->HandleInput(&e);
//            }

//Manager["Hello World"](Manager["Hello World"]("Tileset"),nullptr,(
//                    System::Rect(Manager["Hello World"].Size().first/2-size.first/2,
//                                 Manager["Hello World"].Size().second/2-size.second/2,
//                                 size.first,size.second)));
//            
//            Manager["Hello World"](Manager["Hello World"]("TileTest"),nullptr,(
//                    System::Rect(Manager["Hello World"].Size().first/2-size2.first/2,
//                                 Manager["Hello World"].Size().second/2-size2.second/2,
//                                 size2.first,size2.second)));
            

//        for(int i = 0; i< Map->GetNumTilesets();i++)
//        {
//            std::cout<<"============================\n"
//                     <<"Tileset: "<<i<<"\n"
//                     <<"============================\n";
//            
////            const Tmx::Tileset* tileset = Map->GetTileset(i);
//            
//            std::cout<<"Name: "<<tileset->GetName()<<"\n"
//                     <<"Margin: "<<tileset->GetMargin()<<"\n"
//                     <<"Spaceing: "<<tileset->GetSpacing()<<"\n"
//                     <<"Image width & height: "<<tileset->GetImage()->GetWidth()
//                       <<"|"<<tileset->GetImage()->GetHeight()<<"\n"
//                     <<"Image Source: "<<tileset->GetImage()->GetSource()<<"\n"
//                     <<"Transparent Color(hex): "<<tileset->GetImage()->GetTransparentColor()<<"\n";
//    
//            if(tileset->GetTiles().size() > 0)
//            {
//                const Tmx::Tile* tile = *(tileset->GetTiles().begin());
//                std::map<std::string,std::string> list = tile->GetProperties().GetList();
//                for(auto iter = list.begin(); iter != list.end(); ++iter)
//                {
//                    std::cerr<<iter->first <<" = "<<iter->second<<"\n";
//                }
//            }
//        }






//        LoadTileset(Manager["Hello World"],*Tileset,);
        
//        
        
//        // Iterate through all of the object groups.
//	for (int i = 0; i < Map->GetNumObjectGroups(); ++i) 
//        {
//		printf("                                    \n");
//		printf("====================================\n");
//		printf("Object group : %02d\n", i);
//		printf("====================================\n");
//
//		// Get an object group.
//		const Tmx::ObjectGroup *objectGroup = Map->GetObjectGroup(i);
//
//		// Iterate through all objects in the object group.
//		for (int j = 0; j < objectGroup->GetNumObjects(); ++j)
//                {
//			// Get an object.
//			const Tmx::Object *object = objectGroup->GetObject(j);
//
//			// Print information about the object.
//			printf("Object Name: %s\n", object->GetName().c_str());
//			printf("Object Position: (%03d, %03d)\n", object->GetX(), object->GetY());
//			printf("Object Size: (%03d, %03d)\n", object->GetWidth(), object->GetHeight());
//
//			// Print Polygon points.
//			const Tmx::Polygon *polygon = object->GetPolygon();
//			if (polygon != 0) 
//                        {
//				for (int i = 0; i < polygon->GetNumPoints(); i++)
//                                {
//					const Tmx::Point &point = polygon->GetPoint(i);
//					printf("Object Polygon: Point %d: (%d, %d)\n", i, point.x, point.y);
//				}
//			}
//
//			// Print Polyline points.
//			const Tmx::Polyline *polyline = object->GetPolyline();
//			if (polyline != 0) 
//                        {
//				for (int i = 0; i < polyline->GetNumPoints(); i++)
//                                {
//					const Tmx::Point &point = polyline->GetPoint(i);
//					printf("Object Polyline: Point %d: (%d, %d)\n", i, point.x, point.y);
//				}
//			}
//		}
//	}
        
